package net

import (
	"sync"
	"gopkg.in/oleiade/lane.v1"
	helper2 "myPaxos/helper"
	"github.com/op/go-logging"
	"myPaxos/manager"
	"time"
	"myPaxos/logger"
)

type Transport interface {
	UnicastToID(msg interface{}, id int)
	UnicastToHost(msg interface{}, hostname string)
	Broadcast(msg interface{})
	GetRouters() map[int]*RouterTuple
	GetSelfInfo() *RouterTuple
}

type CommNet struct {
	server      *Server
	clientMap   sync.Map // client map
	failedQueue *lane.Deque
	failedDump  *lane.Deque
	logger      *logging.Logger
	eh          *manager.Eventhub
}

// firstly, provide Eventhub, then call this function
func NewCommNet(eh *manager.Eventhub) *CommNet{
	commNet := &CommNet{
		failedQueue: lane.NewDeque(),
		failedDump:  lane.NewDeque(),
		eh:          eh,
		logger:      logger.GetLogger("net"),
	}
	commNet.initNetwork()
	return commNet
}

func (net *CommNet) initNetwork() {
	info, self, id, remotes, err := helper2.ResolveHosts()
	if err != nil {
		net.logger.Fatal("resolve hosts from config file error: %s", err.Error())
	} else {
		// read config to get server info
		net.server = NewServer(info, self, id, net.eh)
		go net.server.Start()
		for k,v := range remotes {
			net.clientMap.Store(k, NewClient(self, v, k, net.eh))
			net.logger.Debugf("add client for server: %s to map", k)
		}
	}
}

func (net *CommNet) ServerStart() {
	go net.handleFailed()
	net.clientMap.Range(func(key, value interface{}) bool {
		c,ok := value.(*Client)
		if !ok {
			net.logger.Errorf("map items parse error, value is not a client")
		}
		go c.resend() // only do once
		net.logger.Debugf("client: %s try to connect", c.serverHostname)
		sent, err := c.connect()
		if sent && err != nil {
			c.logger.Warningf("build connection to %s failed", c.serverHostname)
			net.failedQueue.Append(c)
		}
		return true
	})
}

func (net *CommNet) handleFailed() {
	timer := time.NewTicker(5*time.Second)
	for {
		select {
		case <-timer.C:
			net.dumpFailed()
			for ;!net.failedDump.Empty(); {
				c := net.failedDump.Pop().(*Client)
				c.connect() // inside, the failed items have been added
			}
		}
	}
}

func (net *CommNet) dumpFailed(){
	for ;!net.failedQueue.Empty(); {
		net.failedDump.Append(net.failedQueue.Pop())
	}
}

// interface provided to outer
func (net *CommNet) UnicastToID(msg interface{}, id int) {

}

func (net *CommNet) UnicastToHost(msg interface{}, hostname string) {

}

func (net *CommNet) Broadcast(msg interface{}) {

}

func (net *CommNet) GetRouters() map[int]*RouterTuple {
	result := make(map[int]*RouterTuple, 0)
	net.server.routers.Range(func(key, value interface{}) bool {
		if i,ok := net.clientMap.Load(key); ok {
			rt := &RouterTuple{
				Hostname: key.(string),
				ID:       i.(*Client).serverID,
			}
			result[rt.ID] = rt
		} else {
			net.logger.Errorf("routers own items that are not in the clientMap")
		}
		return true
	})
	return result
}

func (net *CommNet) GetSelfInfo() *RouterTuple {
	return &RouterTuple{
		Hostname: net.server.hostname,
		ID:       net.server.ID,
	}
}